using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;

namespace ChEngine
{
    // Temporary excluded from build
    public partial class NegamaxEngine:IEngine
    {
        BitBoardDriver board;
        Side engineColor;
        const int TranspositionTableSize = 1799999;
        const int quiesceMaxDepth = 15;
        int ply;
        TranspositionTableEntry[] transpositionTable = new TranspositionTableEntry[TranspositionTableSize];
        //TranspositionTableEntry[] transpositionTableOrder = new TranspositionTableEntry[TranspositionTableSize];
        Side toMove = Side.White;
        #region IEngine Members
        
        public NegamaxEngine(IMoveGeneratorOld generator)
        {
            evaluator = new SimpleBoardEvaluator(generator);
            this.moveGenerator = generator;
        }
        private IBoardEvaluator evaluator;

        public IBoardEvaluator BoardEvaluator
        {
            get { return evaluator; }
            set { evaluator = value; }
        }

        bool escape = false;
        public void StartPlay()
        {
            for (int i = 0; i < killerMoves.GetLength(0); ++i)
            {
                killerMoves[i,0] = new Dictionary<BitMove, BitMove>();
                killerMoves[i, 1] = new Dictionary<BitMove, BitMove>();
            }
            Play();
        }

        public void Move(string move)
        {
            if (toMove != engineColor)
            {
                RealizeMove(move);
                if (board.InCheck(toMove))
                {
                    board.Undo();
                    throw new ChessEngineRecoverableException("Invalid Move:In Check");
                }
                SwitchColor();
                Play();
            }
            else
                throw new ChessEngineRecoverableException("Is not the opponent turn.");
        }

        private void RealizeMove(string move)
        {
            board.RealizeMove(move, toMove);
            moveCount++;
        }
        int moveCount;
        public void Initialize(BitBoardDriver board, Side engineColor, int Ply)
        {
            this.board = board;
            this.engineColor = engineColor;
            this.ply = Ply;
            moveCount = 0;
        }
        string engineMove;
        public string LastMove
        {
            get { return engineMove; }
        }

        public event EventHandler WaitOpponentForMove;

        public event EventHandler EngineMove;

        #endregion
        private void SwitchColor()
        {
            if (toMove == Side.White)
                toMove = Side.Black;
            else
                toMove = Side.White;
        }
        private void LetOpponentMove()
        {
            if (null == WaitOpponentForMove)
            {
                // user cannot play so we exit
                throw new ChessEngineUnrecoverableException("There is no opponent subscribing WaitOtherForMove event...");
            }
            else
            {
                WaitOpponentForMove(this, EventArgs.Empty);
            }
        }
        private void Play()
        {
            
            if (toMove != engineColor)
            {
                LetOpponentMove();
            }
            else
            {
                escape = false;
                engineMove = ComputeLiteralMove();
                if (null != EngineMove)
                    EngineMove(this, EventArgs.Empty);
                if (!string.IsNullOrEmpty(engineMove))
                {
                    RealizeMove(engineMove);
                    if (toMove == Side.White)
                    {
                        board.LasMoveWhite = BitBoardDriver.ParseNotation(engineMove, board, toMove).ToBitMove();
                    }
                    else
                    {
                        board.LasMoveBlack = BitBoardDriver.ParseNotation(engineMove, board, toMove).ToBitMove();
                    }
                }
                SwitchColor();
                LetOpponentMove();
            }
        }

        
        int externDepth = 0;
        //PrincipalVariation principalVariation;
        
        int evaluatedNodes = 0;
        private string ComputeLiteralMove()
        {
            StringBuilder messageBuilder = new StringBuilder(); ;
            int depth = ply;
            //int alpha = -(int.MaxValue-1);
            //int beta = int.MaxValue-1;
            int sval = BoardEvaluator.GetScore(board,engineColor,0);
            int alpha = sval - BitBoardDriver.PawnValue * 300;
            int beta = sval + BitBoardDriver.PawnValue * 300;
            BitMove bm = new BitMove();
            bool movingSideInCheck = board.InCheck(toMove);
            int val = 0;
            
            if (!ProbeBook(ref bm))
            {
                DateTime dt = DateTime.Now;
            
                for (int ideep = 1; ideep <= depth && ideep < 64; ++ideep)
                {

                    evaluatedNodes = 0; 
                    externDepth = ideep;
                    val = Search(engineColor, ideep, alpha, beta, ref bm);
                   
                    if (val >= beta)
                    {
                        beta = int.MaxValue - 1;
                        alpha = -(int.MaxValue - 1);
                        ideep--;
                    }
                    else
                        beta = val + BitBoardDriver.PawnValue * 3;
                    if (val <= alpha)
                    {
                        alpha = -(int.MaxValue - 1);
                        beta = int.MaxValue - 1;
                        ideep--;
                    }
                    else
                        alpha = val- BitBoardDriver.PawnValue * 3;
                    
                    TimeSpan ts = DateTime.Now - dt;
                    messageBuilder.Append(string .Format("{0}\t{1}\t{2}\t{3}\t",ideep,val,ts.Milliseconds/10,evaluatedNodes));
                    AddPrincipalVariationMsg(engineColor, board.Clone() as BitBoardDriver,messageBuilder);
                    OnMessage(messageBuilder.ToString());
                    messageBuilder = new StringBuilder();
                    if (ideep == depth)
                    {
                        if (ts.TotalMilliseconds < 4000)
                        {
                            depth++;
                        }
                    }
                }
                
            }
            escape = false;

            if (val == -BitBoardDriver.CheckMateValue && bm.Current == 0 )
            {
                if (null != EndGame)
                {
                    EndGameEventArgs ea = new EndGameEventArgs();
                    ea.Mates = movingSideInCheck;
                    ea.MateSide = toMove;
                    ea.StaleMates = !movingSideInCheck;
                    EndGame(this, ea);
                }
                return ""; // no moves to do...
            }

            return bm.ToString();
        }

        private void AddPrincipalVariationMsg(Side color,BitBoardDriver bitBoard, StringBuilder messageBuilder)
        {
            BitMove pv;
            while(ProbePV(bitBoard,out pv ))
            {
                
                color = Flip(color);
                if (pv.Current != 0)
                {
                    messageBuilder.Append(pv.ToString());
                    bitBoard.RealizeMove(pv, false);
                }
                else
                {
                    messageBuilder.Append("#");
                    break;
                }
                messageBuilder.Append(" ");
                
            }
        }

        
        private bool useBook = true;
       
        private bool ProbeBook(ref BitMove bm)
        {
            if (null != openingBook && useBook)
            {
                Movement mov;
                if (openingBook.ProbeMove(board.HistoryMoves, out mov))
                {
                    bm = mov.ToBitMove();
                    return true;
                }
                else
                {
                    useBook = false;
                }
            }
            return false;
        }
       
        private int Quiesce( Side color, int alpha, int beta ,int depth)
        {
            evaluatedNodes++;
            int stand_pat;
            
            //TranspositionTableEntry.ValueType valType = TranspositionTableEntry.ValueType.Alpha;
            if (ProbeHash( board, depth, ref alpha, ref beta, out stand_pat))
                return stand_pat;
             stand_pat = BoardEvaluator.GetScore(board, color, moveCount);
             if (alpha >= stand_pat) // from REBEL
                 return stand_pat;
            if (depth == 0)
                return stand_pat;
            
            if (stand_pat >= beta)
            {
                RecordHash(board, depth, stand_pat, TranspositionTableEntry.ValueType.Beta);
                return stand_pat;
            }
            if (alpha < stand_pat)
                alpha = stand_pat;

            List<BitMove> moves = moveGenerator.GetMoves(color, board, MoveGenerationMode.OnlyCaptures);
            moves = ArrangeMoves(moves, color, true, board, depth);
            if (moves.Count == 0)
                return stand_pat;
            SortMoves(moves);
            BitMove bestMove = new BitMove();
            int best = -BitBoardDriver.CheckMateValue;
            
            foreach (BitMove bm in moves)
            {
                //if (bm.Weight < 0)
                //    continue; // here we must apply SEE...
                if (board.IsPseudoMoveLegalWithoutCheckControl(bm, color))
                {
                    board.RealizeMove(bm);
                    if (board.InCheck(color))
                    {
                        board.Undo();
                        continue;
                    }
                    stand_pat = -Quiesce(Flip(color), -beta, -alpha, depth - 1);
                    board.Undo();
                    if (stand_pat > best)
                    {
                        bestMove = bm;
                        RecordPV(board, bm);
                        best = stand_pat;
                    }
                    if (best > alpha)
                        alpha = best;
                    if (best > beta)
                    {
                        int key = RecordHash(board, depth, alpha, TranspositionTableEntry.ValueType.Beta);
                        SetKiller(bm, depth, color);
                        RecordPV(board, null);
                        break;  // beta cutoff
                    }
                }
            }
            // STORE TT
            int index;
            if (best <= alpha)
            {
                index = RecordHash(board, depth, best, TranspositionTableEntry.ValueType.Alpha);
            }
            else
                if (best >= beta)
                {
                    index = RecordHash(board, depth, best, TranspositionTableEntry.ValueType.Beta);
                }
                else
                {
                    index = RecordHash(board, depth, best, TranspositionTableEntry.ValueType.Exact);
                    RecordPV(index, bestMove);
                }
            
            return best;
        }
        private Side Flip(Side color)
        {
            return color == Side.White ? Side.Black : Side.White;
        }
        
        private int Search(Side color, int depth, int alpha, int beta, ref BitMove bestMove/*,ulong prevAttackMask,ulong nextAttackMask*/)
        {
            int score;
            evaluatedNodes++;
            
            if (ProbeHash( board, depth, ref alpha, ref beta, out score) )
            {
                    return score;
            }
            if (depth == 0)
            {
                BitMove dummy = new BitMove();

                //int value = BoardEvaluator.GetScore(board, color, moveCount);
                int value = -Quiesce(Flip(color), -beta, -alpha, quiesceMaxDepth);
                //value = Math.Max(value, qValue);
                //int value;
                
                // value = BoardEvaluator.GetScore(board, color, moveCount);
                // update TT
                if (value <= alpha)
                    RecordHash( board, depth, value, TranspositionTableEntry.ValueType.Alpha);
                else
                    if (value >= beta)
                        RecordHash( board, depth, value, TranspositionTableEntry.ValueType.Beta);
                    else
                    {
                        RecordHash( board, depth, value, TranspositionTableEntry.ValueType.Exact);
                    }

                return value;
            }
            bool inChech = board.InCheck(color);
           
           
            int ttKey = GetTTIndex( board);     
            
            // first check the PV
            BitMove pv;
            int best = -BitBoardDriver.CheckMateValue;

            

            if (ProbePV( board, out pv))
            {
                if (board.IsPseudoMoveAvailable(pv, color))
                {
                    BitMove localMove = new BitMove();
                   
                    board.RealizeMove(pv, false);
                    
                    if (!board.InCheck(color))
                    {
                        int value = -Search(Flip(color), depth - 1, -beta, -alpha, ref localMove);
                        
                        if (value >= beta)
                        {
                            board.Undo();
                            return value;
                        }
                        if (value >= alpha)
                            alpha = value;
                        if (value >= best)
                        {
                            bestMove = pv;
                            best = value;
                        }
                    }
                    board.Undo();
                }
            }

            // next try with killers
            Dictionary<BitMove,BitMove> killersDict = killerMoves[depth,color == Side.White ? 0:1];
            BitMove[] killers = new BitMove[killersDict.Values.Count];
            killersDict.Values.CopyTo(killers, 0);
            foreach (BitMove killer in killers)
            {
                if (board.IsPseudoMoveAvailable(killer, color))
                {
                    BitMove localMove = new BitMove();
                   
                    board.RealizeMove(killer, false);
                    if (!board.InCheck(color))
                    {
                        int value = -Search(Flip(color), depth - 1, -beta, -alpha, ref localMove);
                        board.Undo();
                       
                        if (value >= beta)
                        {
                            bestMove = killer;
                            int key = RecordHash(board, depth, value, TranspositionTableEntry.ValueType.Beta);
                            RecordPV(key, killer);
                            return value;
                        }
                        if (value >= alpha)
                            alpha = value;
                        if (value >= best)
                        {
                            bestMove = killer;
                            best = value;
                        }
                    }
                    else
                        board.Undo();
                    
                }
            }

            // probe null move

            /*
            if (depth > 2 && !inChech  && depth != externDepth)
            {
                BitMove dummy = null;
                board.RealizeMove(new BitMove()); // just force change in side to play:
                // this will set properly the board ZKey
                score = -Search(Flip(color), 2, -beta, -alpha, ref dummy);
                board.Undo();
                if (score >= beta)
                {
                    RecordHash(board, depth, beta, TranspositionTableEntry.ValueType.Beta);
                    return beta;
                }
            }*/

            
            bool cutoff = false;
            int maxPhase = 2;
                
            for (int i = 0; i < maxPhase; ++i) // Different move phases
            {
                List<BitMove> moves;
               
                if (i == 0)
                    moves = moveGenerator.GetMoves(color, board, MoveGenerationMode.OnlyCaptures);
                else
                    moves = moveGenerator.GetMoves(color, board, MoveGenerationMode.OnlyQuiescent);
                
                //    /*
                //     * SORT MOVES
                //     */
                
                moves = ArrangeMoves(moves, color, false, board, depth);
                SortMoves(moves);
                
                int notGoodCount = 0;
                foreach (BitMove bm in moves)
                {
                    if (bm.Equals(pv))
                        continue;
                    if (escape)
                        return alpha;
                    int adjust = 0;
                    if (board.IsPseudoMoveLegalWithoutCheckControl(bm, color))
                    {
                        
#if CHECKED
                        BitBoard clone = board.Clone() as BitBoard;
#endif

                       

                        board.RealizeMove(bm);
                        

                        if (board.InCheck(color))
                        {
                            board.Undo();
#if CHECKED
                            Debug.Assert(board.WhitePawns == clone.WhitePawns);
                            Debug.Assert(board.WhiteRooks == clone.WhiteRooks);
                            Debug.Assert(board.WhiteKnights == clone.WhiteKnights);
                            Debug.Assert(board.WhiteBishops == clone.WhiteBishops);
                            Debug.Assert(board.WhiteQueen == clone.WhiteQueen);
                            Debug.Assert(board.WhiteKing == clone.WhiteKing);

                            Debug.Assert(board.BlackPawns == clone.BlackPawns);
                            Debug.Assert(board.BlackRooks == clone.BlackRooks);
                            Debug.Assert(board.BlackKnights == clone.BlackKnights);
                            Debug.Assert(board.BlackBishops == clone.BlackBishops);
                            Debug.Assert(board.BlackQueen == clone.BlackQueen);
                            Debug.Assert(board.BlackKing == clone.BlackKing);

                            Debug.Assert(board.ZKey == clone.ZKey);
                            Debug.Assert(board.ToMove== clone.ToMove);
#endif
                            continue;
                        }
                        BitMove localMove = new BitMove();

                        if (!inChech && i > 0 ) // no check, no captures
                        {

                            if (externDepth >= 3 && depth > 1 )
                            {
                                if (notGoodCount >= 3 && !MovingADangrousPawn(bm))
                                    adjust = -1;
                            }
                            if (externDepth >= 3 && depth > 2 )
                            {
                                if (notGoodCount >= 5 && !MovingADangrousPawn(bm))
                                    adjust = -1;
                            }
                        }
                        else
                            if (inChech)
                            {
                                adjust = 1;
                            }


                        int value = -Search(Flip(color), depth - 1+adjust, -beta, -alpha, ref localMove);
                        
                        board.Undo();
#if CHECKED
                        Debug.Assert(board.WhitePawns == clone.WhitePawns);
                        Debug.Assert(board.WhiteRooks == clone.WhiteRooks);
                        Debug.Assert(board.WhiteKnights == clone.WhiteKnights);
                        Debug.Assert(board.WhiteBishops == clone.WhiteBishops);
                        Debug.Assert(board.WhiteQueen == clone.WhiteQueen);
                        Debug.Assert(board.WhiteKing == clone.WhiteKing);

                        Debug.Assert(board.BlackPawns == clone.BlackPawns);
                        Debug.Assert(board.BlackRooks == clone.BlackRooks);
                        Debug.Assert(board.BlackKnights == clone.BlackKnights);
                        Debug.Assert(board.BlackBishops == clone.BlackBishops);
                        Debug.Assert(board.BlackQueen == clone.BlackQueen);
                        Debug.Assert(board.BlackKing == clone.BlackKing);

                        Debug.Assert(board.ZKey == clone.ZKey);
                        Debug.Assert(board.ToMove == clone.ToMove);

#endif
                        if (value > best)
                        {
                            bestMove = bm;
                            RecordPV(ttKey, bm);
                            best = value;
                            notGoodCount = 0;
                        }
                        else
                            notGoodCount++;
                        if (best > alpha)
                            alpha = best;
                        if (best > beta)
                        {
                            int key = RecordHash(board, depth, alpha, TranspositionTableEntry.ValueType.Beta);
                            SetKiller(bm, depth,color);
                            RecordPV(ttKey, null);
                            cutoff = true;
                            break;  // beta cutoff
                        }
                    }
                }
                
                if (cutoff)
                    break;
            }
            // STORE TT
            int index;
            if (best <= alpha)
            {
                index = RecordHash( board, depth, best, TranspositionTableEntry.ValueType.Alpha);
            }
            else
                if (best >= beta)
                {
                    index = RecordHash( board, depth, best, TranspositionTableEntry.ValueType.Beta);
                }
                else
                {
                    index = RecordHash( board, depth, best, TranspositionTableEntry.ValueType.Exact);
                    RecordPV(index, bestMove);
                }

            //if (store)
            //    RecordPV(index, bestMove);
           
            if (best == -BitBoardDriver.CheckMateValue) // no move done
            {
                if (inChech)
                    return -BitBoardDriver.CheckMateValue;
                else
                    return 0; // DRAW
            }
            
            return best;
        }

        public bool MovingADangrousPawn(BitMove bm)
        {
            if( (((bm.Previous & board.WhitePawns) != 0 )
                &&((  bm.Current & (BitBoardDriver.Rank1|BitBoardDriver.Rank2) ) != 0 )
                )
                ||
                (((bm.Current & board.BlackPawns) != 0 )
                &&((  bm.Current & (BitBoardDriver.Rank5|BitBoardDriver.Rank6) ) != 0 )))
                return true;
            else
                return false;
        }
        
      
        public void Break()
        {
            escape = true;
        }
        private  void SortMoves(List<BitMove> moves)
        {
            moves.Sort();
        }
        private  List<BitMove> ArrangeMoves(List<BitMove> moves, Side color, bool onlyCaptures,BitBoardDriver board,int depth)
        {
            Side enemy = color == Side.Black ? Side.White : Side.Black;
            List<BitMove> all = new List<BitMove>();
            BitMove prev = color == Side.Black ? board.LasMoveBlack : board.LasMoveWhite;
            ulong opponentKing;
            if (color == Side.White)
                opponentKing = board.BlackKing;
            else
                opponentKing = board.WhiteKing;
            // accept all pseudomoves,
            // further validity check will perform
            // befor real move execution in search...
            foreach (BitMove bm in moves)
            {
                int bonus = board.GetMoveBonus(bm, BitBoardDriver.GamePhase.Middle);
                all.Add(bm);
                bm.Weight = bonus+board.GetMoveCaptureBonus(bm,color);
               
                BitMove pv;
                if( ProbePV(board,out pv) )
                {
                    if (0 == pv.CompareTo(bm))
                        bm.Weight += 100000;
                }
                if (IsKiller(bm, depth,color))
                    bm.Weight += 500;
            }
            
            return all;
        }
    }
}
